#ifndef _NE_UNICODE_H_
#define _NE_UNICODE_H_

#include <neinit.h>

namespace ne
{
    /*              UTF-32                                       UTF-8
          Bit         Code Point          Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6
         7 bit: U-00000000 - U-0000007F: 0xxxxxxx
        11 bit: U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
        16 bit: U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
        21 bit: U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        26 bit: U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
        31 bit: U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
    */

    typedef uint8_t utf8_t;
    typedef uint16_t utf16_t;
    typedef uint32_t utf32_t;

    class Unicode : public std::basic_string<utf32_t>
    {
        public:
            Unicode() : std::basic_string<utf32_t>() {}
            Unicode(const Unicode &s) : std::basic_string<utf32_t>(s) {}

            explicit Unicode(const utf32_t *s) : std::basic_string<utf32_t>(s) {}
            Unicode(const utf32_t *s, size_t n) : std::basic_string<utf32_t>(s, n) {}
            Unicode(size_t n, utf32_t c) : std::basic_string<utf32_t>(n, c) {}

            explicit Unicode(const std::string &s) : std::basic_string<utf32_t>() { fromUTF8(s); }

            explicit Unicode(const char *s) : std::basic_string<utf32_t>() { fromUTF8(std::string(s)); }
            Unicode(const char *s, size_t n) : std::basic_string<utf32_t>() { fromUTF8(std::string(s, n)); }
            Unicode(size_t n, char c) : std::basic_string<utf32_t>() { fromUTF8(std::string(n, c)); }

            virtual ~Unicode() {}

            Unicode& operator = (const Unicode &s)
            {
                std::basic_string<utf32_t>::operator = (s);
                return *this;
            }

            Unicode& operator = (const utf32_t *s)
            {
                std::basic_string<utf32_t>::operator = (s);
                return *this;
            }

            /*
            Unicode& operator = (utf32_t c)
            {
                std::basic_string<utf32_t>::operator = (c);
                return *this;
            }
            */

            Unicode& operator = (const std::string &s)
            {
                fromUTF8(s);
                return *this;
            }

            Unicode& operator = (const char *s)
            {
                fromUTF8(std::string(s));
                return *this;
            }

            /*
            Unicode& operator = (char c)
            {
                return this->operator = (std::string(1, c));
            }
            */

            Unicode& operator += (const Unicode &s)
            {
                return append(s);
            }

            Unicode& operator += (const utf32_t *s)
            {
                return append(s);
            }

            Unicode& operator += (utf32_t c)
            {
                return append(c);
            }

            Unicode& operator += (const std::string &s)
            {
                return append(s);
            }

            Unicode& operator += (const char *s)
            {
                return append(s);
            }

            /*
            Unicode& operator += (char c)
            {
                return append(c);
            }
            */

            Unicode& append(const Unicode &s)
            {
                std::basic_string<utf32_t>::append(s);
                return *this;
            }

            Unicode& append(const utf32_t *s)
            {
                std::basic_string<utf32_t>::append(s);
                return *this;
            }

            Unicode& append(utf32_t c)
            {
                std::basic_string<utf32_t>::append(1, c);
                return *this;
            }

            Unicode& append(const std::string &s)
            {
                return append(Unicode(s));
            }

            Unicode& append(const char *s)
            {
                return append(std::string(s));
            }

            /*
            Unicode& append(char c)
            {
                return append(std::string(1, c));
            }
            */

            void fromUTF8(const std::string &s)
            {
                clear();

                size_t len = s.length();
                if (len > 0)
                {
                    size_t i = 0;
                    while (i < len)
                    {
                        utf8_t c = s[i];
                        if (c <= 0x7F)
                        {
                            utf32_t code = 0x7F & c;
                            ++i;
                            append(code);
                        }
                        else if (c <= 0xDF)
                        {
                            utf32_t code = 0x1F & c;
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            ++i;
                            append(code);
                        }
                        else if (c <= 0xEF)
                        {
                            utf32_t code = 0xF & c;
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            ++i;
                            append(code);
                        }
                        else if (c <= 0xF7)
                        {
                            utf32_t code = 0x7 & c;
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            ++i;
                            append(code);
                        }
                        else if (c <= 0xFB)
                        {
                            utf32_t code = 0x3 & c;
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            ++i;
                            append(code);
                        }
                        else if (c <= 0xFD)
                        {
                            utf32_t code = 0x1 & c;
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            code <<= 6;
                            c = s[++i];
                            if (0x80 <= c && c <= 0xBF)
                            {
                                code |= (0x3F & c);
                            }
                            ++i;
                            append(code);
                        }
                        else
                        {
                            return;
                        }
                    }
                }
            }

            std::string toUTF8() const
            {
                std::string s;
                size_t len = length();
                if (len > 0)
                {
                    for (size_t i=0; i<len; ++i)
                    {
                        utf32_t code = at(i);
                        if (code < 0x80)
                        {
                            s += static_cast<utf8_t>(0x7F | code);
                        }
                        else if (code < 0x800)
                        {
                            s += static_cast<utf8_t>(0xC0 | ((0x7C0 & code) >> 6));
                            s += static_cast<utf8_t>(0x80 | (0x3F & code));
                        }
                        else if (code < 0x10000)
                        {
                            s += static_cast<utf8_t>(0xE0 | ((0xF000 & code) >> 12));
                            s += static_cast<utf8_t>(0x80 | ((0xFC0 & code) >> 6));
                            s += static_cast<utf8_t>(0x80 | (0x3F & code));
                        }
                        else if (code < 0x200000)
                        {
                            s += static_cast<utf8_t>(0xF0 | ((0x1C0000 & code) >> 18));
                            s += static_cast<utf8_t>(0x80 | ((0x3F000 & code) >> 12));
                            s += static_cast<utf8_t>(0x80 | ((0xFC0 & code) >> 6));
                            s += static_cast<utf8_t>(0x80 | (0x3F & code));
                        }
                        else if (code < 0x4000000)
                        {
                            s += static_cast<utf8_t>(0xF8 | ((0x3000000 & code) >> 24));
                            s += static_cast<utf8_t>(0x80 | ((0xFC0000 & code) >> 18));
                            s += static_cast<utf8_t>(0x80 | ((0x3F000 & code) >> 12));
                            s += static_cast<utf8_t>(0x80 | ((0xFC0 & code) >> 6));
                            s += static_cast<utf8_t>(0x80 | (0x3F & code));
                        }
                        else if (code < 0x80000000)
                        {
                            s += static_cast<utf8_t>(0xFC | ((0x40000000 & code) >> 30));
                            s += static_cast<utf8_t>(0x80 | ((0x3F000000 & code) >> 24));
                            s += static_cast<utf8_t>(0x80 | ((0xFC0000 & code) >> 18));
                            s += static_cast<utf8_t>(0x80 | ((0x3F000 & code) >> 12));
                            s += static_cast<utf8_t>(0x80 | ((0xFC0 & code) >> 6));
                            s += static_cast<utf8_t>(0x80 | (0x3F & code));
                        }
                    }
                }
                return s;
            }
    };
}

#endif
